using System.ComponentModel;
using System.Collections.Generic;
using System.Runtime.Serialization;

using SharpGL.Media3D;
using SharpGL.SceneGraph.Primitives;
using SharpGL.SceneGraph.Assets;

namespace SharpGL.SceneGraph
{
    /// <summary>
    /// A Face is a set of indices to vectors.
    /// </summary>
    [TypeConverter(typeof(System.ComponentModel.ExpandableObjectConverter))]
    [DataContract(/*IsReference = true, */Name = "Face", Namespace = "SharpGL.SceneGraph")]
    public class Face : IHasMaterial
    {
        #region Private Fields

        /// <summary>
        /// The indices.
        /// </summary>
        [DataMember()]
        private List<Index> indices = new List<Index>();

        /// <summary>
        /// The neighbor indices.
        /// </summary>
        [DataMember()]
        private int[] neighbourIndices = null;

        /// <summary>
        /// The material.
        /// </summary>
        [DataMember()]
        private Material material = null;

        #endregion Private Fields

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="Face"/> class.
        /// </summary>
        public Face() 
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Face"/> class.
        /// </summary>
        public Face(Face face)
        {
            if (face != null)
            {
                foreach (Index index in face.indices)
                    indices.Add(new Index(index));

                material = face.material;
                if (face.material != null && face.material.Texture != null)
                    material.Texture = (Texture2D)face.material.Texture.Clone();
            }
        }

        #endregion Constructors

        #region Public Methods

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// a <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return "Face, " + this.indices.Count + " indices";
        }

        /// <summary>
        /// Returns the plane equation (ax + by + cz + d = 0) of the face.
        /// </summary>
        /// <param name="parent">The parent polygon.</param>
        /// <returns>An array of four coefficients a, b, c, d.</returns>
        public float[] GetPlaneEquation(Polygon parent)
        {
            float a = 0.0f;
            float b = 0.0f;
            float c = 0.0f;
            float d = 0.0f;
            
            //	Do we have enough vectices for a normal?
            if (this.indices.Count >= 3)
            {
                //	Get refs to vectors.
                Point3D v1 = parent.Vertices[this.indices[0].Vertex];
                Point3D v2 = parent.Vertices[this.indices[1].Vertex];
                Point3D v3 = parent.Vertices[this.indices[2].Vertex];

                a = v1.Y * (v2.Z - v3.Z) + v2.Y * (v3.Z - v1.Z) + v3.Y * (v1.Z - v2.Z);
                b = v1.Z * (v2.X - v3.X) + v2.Z * (v3.X - v1.X) + v3.Z * (v1.X - v2.X);
                c = v1.X * (v2.Y - v3.Y) + v2.X * (v3.Y - v1.Y) + v3.X * (v1.Y - v2.Y);
                d = -(v1.X * (v2.Y * v3.Z - v3.Y * v2.Z) +
                      v2.X * (v3.Y * v1.Z - v1.Y * v3.Z) +
                      v3.X * (v1.Y * v2.Z - v2.Y * v1.Z));
            }

            return new float[] { a, b, c, d };
        }

        /// <summary>
        /// Gets the surface normal.
        /// </summary>
        /// <param name="parent">The parent polygon.</param>
        /// <returns>Face normal.</returns>
        public Vector3D GetSurfaceNormal(Polygon parent)
        {
            //	Do we have enough vectices for a normal?
            if (this.indices.Count < 3)
                return new Vector3D(0, 0, 0);

            Point3D v1 = parent.Vertices[this.indices[0].Vertex];
            Point3D v2 = parent.Vertices[this.indices[1].Vertex];
            Point3D v3 = parent.Vertices[this.indices[2].Vertex];
            Vector3D va = v1 - v2;
            Vector3D vb = v2 - v3;
            
            //return Vector3D.CrossProduct(va, vb);
            
            Vector3D normal = Vector3D.CrossProduct(va, vb);
            normal.Normalize();

            return normal;
        }

        /// <summary>
        /// This function reverses the order of the indices, i.e changes which direction
        /// this face faces in.
        /// </summary>
        /// <param name="parent">The parent polygon.</param>
        public void Reorder(Polygon parent)
        {
            //	Create a new index collection.
            List<Index> newIndices = new List<Index>();

            //	Go through every old index and add it.
            for (int i = 0; i < this.indices.Count; i++)
                newIndices.Add(this.indices[this.indices.Count - (i + 1)]);

            //	Set the new index array.
            this.indices = newIndices;

            //	Recreate each normal.
            GenerateNormals(parent);
        }

        /// <summary>
        /// This function generates normals for every point.
        /// </summary>
        /// <param name="parent">The parent polygon.</param>
        public void GenerateNormals(Polygon parent)
        {
            //	Do we have enough vectices for a normal?
            if (this.indices.Count >= 3)
            {
                foreach (Index index in this.indices)
                {
                    //	Create a normal.
                    Vector3D vNormal = GetSurfaceNormal(parent);
                    vNormal.Normalize();

                    //	Add it to the normals, setting the index for next time.
                    if (index.Normal != -1)
                        parent.Normals.RemoveAt(index.Normal);
                    index.Normal = parent.Normals.Count;
                    parent.Normals.Add(vNormal);
                }
            }
        }

        #endregion Public Methods

        #region Public Properties

        /// <summary>
        /// Gets the count.
        /// </summary>
        public int Count
        {
            get { return this.indices.Count; }
        }

        /// <summary>
        /// Gets or sets the indices.
        /// </summary>
        /// <value>
        /// The indices.
        /// </value>
        public List<Index> Indices
        {
            get { return this.indices; }
            protected set { this.indices = value; }
        }

        /// <summary>
        /// Gets or sets the neighbour indicies.
        /// </summary>
        /// <value>
        /// The neighbour indicies.
        /// </value>
        public int[] NeighbourIndices
        {
            get { return this.neighbourIndices; }
            set { this.neighbourIndices = value; }
        }

        /// <summary>
        /// Gets or sets the material.
        /// </summary>
        /// <value>
        /// The material.
        /// </value>
        public Material Material
        {
            get { return this.material; }
            set { this.material = value; }
        }

        #endregion Public Properties
    }
}